#include "stdafx.h"
#include "HXWindow.h"
#include "HXDirector.h"
#include "HXLink.h"
#include "HXTalk.h"
#include "HXEvents.h"

//=============================================================================

HXMainWindow g_window;				// display subsystem
HXLogger g_logger;					// event logging
HXSubtitle g_subtitle;				// speech subtitle
HWND g_keyInputDlg = 0;				// for DialogDirectSpeak

//=============================================================================

//! Message handler for change voice dialog.
INT_PTR CALLBACK DialogChangeVoice(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
	{
		// get the list of available mary voices
		vector<string> voices;
		g_speechtts.GetVoices(voices);

		// get handle to the list
		HWND hwndList = GetDlgItem(hDlg,IDC_VOICELIST);

		// populate the list and create data strings
		vector<string>::iterator voiceIter;
		for(voiceIter = voices.begin(); voiceIter != voices.end(); ++voiceIter) 
		{
			// set the viewing string
			wstring voice = StringToWString(*voiceIter).c_str();
			int id = SendMessage(hwndList, LB_ADDSTRING, 0,(LPARAM)voice.c_str());	

			// set the currently used voice to be selected
			if (voice == g_config.voiceName)
			{
               SendMessage(hwndList, LB_SETCURSEL, id, 0);
			}

			// create and set the data string
			char* s = new char[100];
			strcpy_s(s,100,voiceIter->c_str());	
			SendMessage(hwndList, LB_SETITEMDATA, id,(LPARAM)s);
		}

		// set focus onto the list
		SetFocus(hwndList); 

		return (INT_PTR)TRUE;
	}
	case WM_COMMAND:
		if (LOWORD(wParam) == IDC_VOICELIST && (HIWORD(wParam) == LBN_DBLCLK || HIWORD(wParam) == LBN_SELCHANGE))
		{

			HWND hwndList = GetDlgItem(hDlg,IDC_VOICELIST);

			// Get selected index.
			int nItem = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0); 

			//int len = SendMessage(hwndList, LB_GETTEXTLEN, nItem, 0);
			//char* test = new char[len*2];
			//int ret = SendMessage(hwndList, LB_GETTEXT, nItem, (LPARAM)test);
			

			// Get item data.
			char* selectedVoice = NULL;
			selectedVoice = (char*)SendMessage(hwndList, LB_GETITEMDATA, nItem, 0);

			// set the global config voice to the newly selected voice
			g_config.voiceName = StringToWString(string(selectedVoice));

			g_window.ShowStatus(L"Set voice to: \"%s\"", g_config.voiceName.c_str());
			g_speechtts.SetVoice(WStringToString(g_config.voiceName)); // change voice!
			
			// close if double-click
			if (!(LOWORD(wParam) == IDC_VOICELIST && HIWORD(wParam) == LBN_SELCHANGE))
			{
				EndDialog(hDlg, LOWORD(wParam));
			}
			
			return (INT_PTR)TRUE;
		}
		else if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			//EndDialog(hDlg, LOWORD(wParam));
			DestroyWindow(hDlg);
			return (INT_PTR)TRUE;
		}
		break;	
	case WM_DESTROY:
	{
		// clean up the data strings
		HWND hwndList = GetDlgItem(hDlg,IDC_VOICELIST);
		int n = SendMessage(hwndList, LB_GETCOUNT, 0, 0);
		for ( int i = 0; i < n; i++ )
		{
			char* s = (char*)SendMessage(hwndList, LB_GETITEMDATA, i, 0);
			delete[] s;
		}
		return (INT_PTR)TRUE;
	}
	}
	return (INT_PTR)FALSE;
}


//! Message handler for speak box.
INT_PTR CALLBACK DialogDirectSpeak(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	bool success = false;
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_ACTIVATE:
        if (0 == wParam)             // becoming inactive
			g_keyInputDlg = NULL;
        else                         // becoming active
            g_keyInputDlg = hDlg;
		break;

	case WM_COMMAND:
		if ((LOWORD(wParam) == IDC_SENDBUTTON) || (LOWORD(wParam) == IDC_SENDCLEARBUTTON))
		{
			wchar_t text[MAX_TEXT_LENGTH];
			if (GetDlgItemText(hDlg, IDC_EDIT1, text, MAX_TEXT_LENGTH))
			{
				if (g_config.bEchoInputs)
				{
					AsyncSpeak(wstring(text));
					g_window.ShowStatus(L"Speaking text: \"%s\"", text);
				}
				else
				{
					g_logger.Log(text, L'I');
					g_link.SetUserText(WStringToString(wstring(text)).c_str());
				}
				SetFocus(GetDlgItem(hDlg, IDC_EDIT1));
				success = true;
			}
		}
		if ((LOWORD(wParam) == IDC_CLEARBUTTON) || (LOWORD(wParam) == IDC_SENDCLEARBUTTON))
		{
			SetDlgItemText(hDlg, IDC_EDIT1, L"");
			success = true;
		}
		if (LOWORD(wParam) == IDCANCEL) 
		{
			g_keyInputDlg = NULL;
			DestroyWindow(hDlg);
			success = true;
		}
		break;
	}
	return success ? (INT_PTR)TRUE : (INT_PTR)FALSE;
}

//! Message handler for about box.
INT_PTR CALLBACK DialogAbout(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			//EndDialog(hDlg, LOWORD(wParam));
			DestroyWindow(hDlg);
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

//! Message handler for Synapse information dialog.
INT_PTR CALLBACK DialogSynapseID(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		SetDlgItemText(hDlg, IDC_STATIC, g_link.GetCapabilityString().c_str());
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			DestroyWindow(hDlg);
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

//-----------------------------------------------------------------------------

//! Constructor.
HXMainWindow::HXMainWindow()
: m_hFramedWnd(0),
  m_hLayerWnd(0),
  m_hStatWnd(0),
  m_hMenu(0),
  m_bFullscreen(false),
  m_bWindowHasBorder(true),
  m_bWindowOnTop(false)
{
	m_statusBuffer[0] = 0;
}

//! Initialize this window.
bool HXMainWindow::Create(int nCmdShow)
{
	if (!(m_hFramedWnd = CreateWindow(g_main.szWindowClass, g_main.szTitle, 
								WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
								CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, g_main.hInst, NULL)))
	{
		return false;
	}

	RECT r;
	GetWindowRect(m_hFramedWnd, &r);

	// Create layered (invisible) window
	m_hLayerWnd = CreateWindowEx(WS_EX_LAYERED, g_main.szWindowClass, g_main.szTitle, 
						WS_POPUP,
						r.left, r.top, CW_USEDEFAULT, 0, m_hFramedWnd, NULL, g_main.hInst, NULL);

	// Create status bar
	m_hStatWnd = CreateStatusBar(m_hFramedWnd, 0, g_main.hInst, 1); // 1 part

	UpdateViewMenu();
	ShowWindow(m_hLayerWnd, SW_HIDE);
	ShowWindow(m_hFramedWnd, nCmdShow);
	UpdateWindow(m_hFramedWnd);

	return true;
}

//! Close this window.
void HXMainWindow::Destroy()
{
	// nothing
}

//! Show specified text in status subwindow.
void HXMainWindow::ShowStatus(const wchar_t* a_Text, ...)
{
    // Format the input string
    va_list pArgs;
    va_start(pArgs, a_Text);

	StringCchVPrintfW(m_statusBuffer, MAX_STATUS_LENGTH-1, a_Text, pArgs);
	
    va_end(pArgs);

    // Ensure that the formatted string is NULL-terminated
    m_statusBuffer[MAX_STATUS_LENGTH-1] = L'\0';

	// Display in status bar
	SendNotifyMessage(m_hStatWnd, SB_SETTEXT, 0, (LPARAM)m_statusBuffer);
	//InvalidateRect(m_hStatWnd, NULL, true);
}

//! Show specified text in status subwindow, and in separate dialogue if debugging.
void HXMainWindow::ShowErrorStatus(const wchar_t* a_Text, ...)
{
    va_list pArgs;
    va_start(pArgs, a_Text);

	StringCchVPrintfW(m_statusBuffer, MAX_STATUS_LENGTH-1, a_Text, pArgs);

	va_end(pArgs);

    m_statusBuffer[MAX_STATUS_LENGTH-1] = L'\0';
	g_logger.Log(m_statusBuffer, L'E');
    MessageBox(NULL, m_statusBuffer, L"Head X Error", MB_OK | MB_ICONERROR);
	SendNotifyMessage(m_hStatWnd, SB_SETTEXT, 0, (LPARAM)m_statusBuffer);
	//UpdateWindow(m_hFramedWnd);
	//InvalidateRect(m_hStatWnd, NULL, true);
}

//****************************************************************************

//! Change the relative size of the specified window by the given parameters.
void HXMainWindow::RelativeResize(int width, int height)
{
	if (width || height)
	{
		RECT r;
		GetWindowRect(m_hFramedWnd, &r);
		SetWindowPos(m_hFramedWnd, 
					 0, 
					 0, 0, // ignore position
					 r.right - r.left + width, r.bottom - r.top + height,
					 SWP_NOMOVE | SWP_NOZORDER); 
		// not necessary....
		/*GetWindowRect(m_hLayerWnd, &r);
		SetWindowPos(m_hLayerWnd, 
					 0, 
					 0, 0, // ignore position
					 r.right - r.left + width, r.bottom - r.top + height,
					 SWP_NOMOVE | SWP_NOZORDER);*/
	}
}

//! Change the size of the specified window to the given parameters.
void HXMainWindow::Resize(int width, int height)
{
	if (width > 0 && height > 0)
	{
		SetWindowPos(m_hFramedWnd, 
					 0, 
					 0, 0, // ignore position
					 width, height,
					 SWP_NOMOVE | SWP_NOZORDER); 
		SetWindowPos(m_hLayerWnd, 
					 0, 
					 0, 0, // ignore position
					 width, height,
					 SWP_NOMOVE | SWP_NOZORDER); 
	}
}

//! Change the relative position of the specified window by the given parameters.
void HXMainWindow::RelativeReposition(int x, int y)
{
	if (x || y)
	{
		RECT r;
		GetWindowRect(m_hFramedWnd, &r);
		SetWindowPos(m_hFramedWnd, 
					 0, 
					 r.left + x, r.top + y,
					 0, 0, // ignore size
					 SWP_NOSIZE | SWP_NOZORDER);
		GetWindowRect(m_hLayerWnd, &r);
		SetWindowPos(m_hLayerWnd, 
					 0, 
					 r.left + x, r.top + y,
					 0, 0, // ignore size
					 SWP_NOSIZE | SWP_NOZORDER);
	}
}

//! Change the position of the specified window  to the given parameters.
void HXMainWindow::Reposition(int x, int y)
{
	SetWindowPos(m_hFramedWnd, 
				 0, 
				 x, y,
				 0, 0, // ignore size
				 SWP_NOSIZE | SWP_NOZORDER);
	SetWindowPos(m_hLayerWnd, 
				 0, 
				 x, y,
				 0, 0, // ignore size
				 SWP_NOSIZE | SWP_NOZORDER);
}

//****************************************************************************

//! Open a Head X configuration file on startup (do not call later - resets working path to startup path).
bool HXMainWindow::StartupConfigOpen()
{
	if (wcslen(g_paths.GetStartupFile()) > 0)
	{
		RunSimpleThread(IDM_CONFIGOPEN);
		//g_director.AsyncExecuteP(L"loadconfig %s", configArg.c_str());
		//while (!g_link.GetIsReady()) { Sleep(10); }
		//g_paths.SetWorkingPath(wstring(g_paths.GetStartupPath()));
		if (g_link.GetLastError() == HXSY_NONE)
		{
			return true;
		}
	}
	EnableMenuItem(GetFileMenu(), IDM_CONFIGREOPEN, MF_GRAYED);
	return false;
}

//! Load a Head X configuration file.
void HXMainWindow::ClickConfigOpen()
{
	OPENFILENAME ofn;
	ZeroMemory(&ofn, sizeof(ofn));

	WCHAR szFileName[MAX_PATH];
	szFileName[0] = L'\0';

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hFramedWnd;
	ofn.hInstance = g_main.hInst;
	ofn.lpstrFilter = L"Head X Configuration (*.hxml/hxm)\0*.hxml;*.hxm\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn))
	{
		g_director.AsyncExecuteP(L"loadconfig %s", szFileName);
		if (g_link.GetLastError() == HXSY_NONE)
		{
			EnableMenuItem(GetFileMenu(), IDM_CONFIGREOPEN, MF_ENABLED);
		}
		else
		{
			EnableMenuItem(GetFileMenu(), IDM_CONFIGREOPEN, MF_GRAYED);
		}
	}
}

//! Reloads the last loaded Head X configuration file.
void HXMainWindow::ClickConfigReopen()
{
	wstring path(g_paths.GetLastFilePath());
	path.append(L"\\");
	path.append(g_paths.GetLastFileName());
	g_director.AsyncExecuteP(L"loadconfig %s", path.c_str());
}


//! Load a Head X configuration file.
void HXMainWindow::ClickParamOpen()
{
	OPENFILENAME ofn;
	ZeroMemory(&ofn, sizeof(ofn));

	WCHAR szFileName[MAX_PATH];
	szFileName[0] = L'\0';

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hFramedWnd;
	ofn.hInstance = g_main.hInst;
	ofn.lpstrFilter = L"Head X Configuration (*.hxml/hxm)\0*.hxml;*.hxm\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn))
	{
		g_director.AsyncExecuteP(L"loadparam %s", szFileName);
		if (g_link.GetLastError() == HXSY_NONE)
		{
			EnableMenuItem(GetFileMenu(), IDM_REAPPLYPARAMS, MF_ENABLED);
		}
		else
		{
			EnableMenuItem(GetFileMenu(), IDM_REAPPLYPARAMS, MF_GRAYED);
		}
	}
}

//! Reloads the last applied Head X parameter file.
void HXMainWindow::ClickParamReopen()
{
	wstring path(g_paths.GetLastFilePath());
	path.append(L"\\");
	path.append(g_paths.GetLastFileName());
	g_director.AsyncExecuteP(L"loadparam %s", path.c_str());
}


//! Clear any previously loaded configuration.
void HXMainWindow::ClickConfigClear()
{
	g_director.AsyncExecute(L"clearconfig");
}

//! Select a background image.
void HXMainWindow::ClickBackgroundOpen()
{
	OPENFILENAME ofn;
	ZeroMemory(&ofn, sizeof(ofn));

	WCHAR szFileName[MAX_PATH];
	szFileName[0] = L'\0';

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hFramedWnd;
	ofn.hInstance = g_main.hInst;
	ofn.lpstrFilter = L"Image Files (*.bmp/jpg/png)\0*.bmp;*.jpg;*.png\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn))
	{
		g_director.AsyncExecuteP(L"loadbg %s", szFileName);
	}
}

//! Removes the background image.
void HXMainWindow::ClickBackgroundClear()
{
	g_director.AsyncExecute(L"clearbg");
}

//! Add a 3D model.
void HXMainWindow::ClickModelAdd()
{
	OPENFILENAME ofn;
	ZeroMemory(&ofn, sizeof(ofn));

	WCHAR szFileName[MAX_PATH];
	szFileName[0] = L'\0';

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hFramedWnd;
	ofn.hInstance = g_main.hInst;
	ofn.lpstrFilter = L"Head X Configuration (*.hxml/hxm)\0*.hxml;*.hxm\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn))
	{
		g_director.AsyncExecuteP(L"loadmodel %s", szFileName);
	}
}

//! Clear all 3D models.
void HXMainWindow::ClickModelClear()
{
	g_director.AsyncExecute(L"clearmodels");
}

//! Remove the specified 3D model.
void HXMainWindow::ClickModelRemove(int id)
{
	HMENU hMenuSub = GetModelsMenu();
	int iMenuItems = GetMenuItemCount(hMenuSub);
	if (id < iMenuItems)
	{
		wchar_t entryName[MAX_TEXT_LENGTH];
		GetMenuString(hMenuSub, id, entryName, MAX_TEXT_LENGTH, MF_BYPOSITION);
		g_director.AsyncExecuteP(L"removemodel %s", entryName);
	}
}

//! Update the model submenu to reflect the currently loaded 3D models.
void HXMainWindow::UpdateModelsMenu()
{
	if (!m_hMenu)
	{
		HMENU hMenuSub = GetModelsMenu();

		// Clean the sub menu
		int iMenuItems = GetMenuItemCount(hMenuSub);
		if (iMenuItems == -1)
		{
			ShowErrorStatus(L"Failed to clean Models menu.");
			return;
		}
		else if (iMenuItems > 0)
		{
			for (int i = 0; i < iMenuItems; i++)
			{
				RemoveMenu(hMenuSub, 0, MF_BYPOSITION);
			}
		}

		list<HXResource>::iterator resIt = g_config.resourceGroup.resources.begin();
		if (!g_config.resourceGroup.Size())
		{
			AppendMenu(hMenuSub, MF_STRING, IDM_NONE, L"(no models loaded)");
			EnableMenuItem(hMenuSub, IDM_NONE, MF_GRAYED);
		}
		else if (!g_config.resourceGroup.children.size())
		{
			AppendMenu(hMenuSub, MF_STRING, IDM_NONE, L"(no selectable groups)");
			EnableMenuItem(hMenuSub, IDM_NONE, MF_GRAYED);
		}
		else
		{
			UINT uIndex	= 0;			
			list<HXResourceGroup*> groupPtrs;
			g_config.resourceGroup.ExpandGroup(groupPtrs);
			for (list<HXResourceGroup*>::iterator gpIt = groupPtrs.begin(); gpIt != groupPtrs.end(); ++gpIt)
			{
				if ((*gpIt)->Size())
				{
					InsertMenu(hMenuSub, (UINT)-1, MF_BYPOSITION | MF_STRING, IDM_ACCESSORY0 + uIndex, (*gpIt)->name.c_str());
					++uIndex;
					if (uIndex > (IDM_ACCESSORYX - IDM_ACCESSORY0)) 
						break;
				}
			}
			/*list<HXResource*> resPtrs;
			g_config.resourceGroup.Expand(resPtrs);
			for (list<HXResource*>::iterator rpIt = resPtrs.begin(); rpIt != resPtrs.end(); ++rpIt)
			{
				wchar_t entryName[2*MAX_PATH+10];
				if ((*rpIt)->textureName.empty())
				{
					swprintf_s(entryName, 2*MAX_PATH+10, L"%s", (*rpIt)->modelName.c_str());
				}
				else
				{
					swprintf_s(entryName, 2*MAX_PATH+10, L"%s (Texture: %s)", (*rpIt)->modelName.c_str(), (*rpIt)->textureName.c_str());
				}
				InsertMenu(hMenuSub, 0, MF_BYPOSITION | MF_STRING, IDM_ACCESSORY0 + uIndex, entryName);
				++uIndex;
				if (uIndex > (IDM_ACCESSORYX - IDM_ACCESSORY0)) 
					break;
			}*/
		}
	}
}

//! Export currently loaded 3D models into a specified, new HX3 file.
void HXMainWindow::ClickModelsExport()
{
	OPENFILENAME ofn;
	ZeroMemory(&ofn, sizeof(ofn));

	WCHAR szFileName[MAX_PATH];
	szFileName[0] = L'\0';

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hFramedWnd;
	ofn.hInstance = g_main.hInst;
	ofn.lpstrDefExt = L"hx3";
	ofn.lpstrFilter = L"Head X Model Archive (*.hx3)\0*.hx3\0All Files (*.*)\0*.*\0";
	ofn.lpstrFile = szFileName;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN;

	if (GetSaveFileName(&ofn))
	{
		g_director.AsyncExecuteP(L"exportmodels %s", szFileName);
	}
}

//***********************

//! Update the quick action submenu from current configuration.
void HXMainWindow::UpdateQuickMenu()
{
	HMENU hMenuSub = GetQuickActionMenu();

    // Clean the sub menu
    int iMenuItems = GetMenuItemCount(hMenuSub);
    if (iMenuItems > 0)
    {
        for (int i = 0; i < iMenuItems; i++) // leave the Add and Clear options
        {
            RemoveMenu(hMenuSub, 0, MF_BYPOSITION);
        }
    }

	if (g_config.quickAction1_label.size())
	{
		wstring menustring(g_config.quickAction1_label);
		menustring.append(L"\tCtrl-1");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_1, menustring.c_str());
	}
	if (g_config.quickAction2_label.size())
	{
		wstring menustring(g_config.quickAction2_label);
		menustring.append(L"\tCtrl-2");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_2, menustring.c_str());
	}
	if (g_config.quickAction3_label.size())
	{
		wstring menustring(g_config.quickAction3_label);
		menustring.append(L"\tCtrl-3");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_3, menustring.c_str());
	}
	if (g_config.quickAction4_label.size())
	{
		wstring menustring(g_config.quickAction4_label);
		menustring.append(L"\tCtrl-4");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_4, menustring.c_str());
	}
	if (g_config.quickAction5_label.size())
	{
		wstring menustring(g_config.quickAction5_label);
		menustring.append(L"\tCtrl-5");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_5, menustring.c_str());
	}
	if (g_config.quickAction6_label.size())
	{
		wstring menustring(g_config.quickAction6_label);
		menustring.append(L"\tCtrl-6");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_6, menustring.c_str());
	}
	if (g_config.quickAction7_label.size())
	{
		wstring menustring(g_config.quickAction7_label);
		menustring.append(L"\tCtrl-7");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_7, menustring.c_str());
	}
	if (g_config.quickAction8_label.size())
	{
		wstring menustring(g_config.quickAction8_label);
		menustring.append(L"\tCtrl-8");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_8, menustring.c_str());
	}
	if (g_config.quickAction9_label.size())
	{
		wstring menustring(g_config.quickAction9_label);
		menustring.append(L"\tCtrl-9");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_9, menustring.c_str());
	}
	if (g_config.quickAction0_label.size())
	{
		wstring menustring(g_config.quickAction0_label);
		menustring.append(L"\tCtrl-0");
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_0, menustring.c_str());
	}

	if (!GetMenuItemCount(hMenuSub))
		AppendMenu(hMenuSub, MF_STRING, IDM_QUICKACTION_NONE, L"(none)");
}

//! Toggle visibility of subtitles or input prompt.
void HXMainWindow::ClickSubtitle(bool isInputPrompt)
{
	HMENU hMenuSub = GetSubtitleMenu();
	if (isInputPrompt) // on-screen input prompt
	{
		if (g_config.bSubtitleInput)
		{
			g_config.bSubtitleInput = false;
			g_subtitle.Clear();
			ShowStatus(L"Input prompt disabled");
		}
		else
		{
			g_config.bSubtitleInput = true;
			g_config.bSubtitleOutput = false;
			ShowStatus(L"Input prompt enabled");
		}
	}
	else // speech subtitles
	{
		if (g_config.bSubtitleOutput)
		{
			g_config.bSubtitleOutput = false;
			ShowStatus(L"Speech subtitles disabled");
		}
		else
		{
			g_config.bSubtitleOutput = true;
			g_config.bSubtitleInput = false;
			ShowStatus(L"Speech subtitles enabled");
		}
	}
	CheckMenuItem(hMenuSub, IDM_SUBTITLEINPUT, g_config.bSubtitleInput ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_SUBTITLEOUTPUT, g_config.bSubtitleOutput ? MF_CHECKED : MF_UNCHECKED);
}

//! Switch between processing any input or just echoing it.
void HXMainWindow::ClickSetEchoInputs()
{
	HMENU hMenuSub = GetActionMenu();
	if (g_config.bEchoInputs)
	{
		g_config.bEchoInputs = false;
		CheckMenuItem(hMenuSub, IDM_ECHOINPUTS, MF_UNCHECKED);
		ShowStatus(L"Inputs will not be echoed");
	}
	else
	{
		g_config.bEchoInputs = true;
		CheckMenuItem(hMenuSub, IDM_ECHOINPUTS, MF_CHECKED);
		ShowStatus(L"Inputs will be echoed");
	}
}

//! Enable/disable text processing.
void HXMainWindow::ClickParseInput()
{
	HMENU hMenuSub = GetActionMenu();
	if (g_config.bCurlyTags)
	{
		g_config.bCurlyTags = false;
		CheckMenuItem(hMenuSub, IDM_PARSEINPUT, MF_UNCHECKED);
	}
	else
	{
		g_config.bCurlyTags = true;
		CheckMenuItem(hMenuSub, IDM_PARSEINPUT, MF_CHECKED);
	}
}

//! Automatically break speech between sentences (enable/disable).
void HXMainWindow::ClickBreakOnStop()
{

	HMENU hMenuSub = GetActionMenu();
	if (g_talk.BreakOnStop())
	{
		g_talk.BreakOnStop() = false;
		CheckMenuItem(hMenuSub, IDM_BOS, MF_UNCHECKED);
	}
	else
	{
		g_talk.BreakOnStop() = true;
		CheckMenuItem(hMenuSub, IDM_BOS, MF_CHECKED);
	}
}

//! Synapse controls.
void HXMainWindow::ClickSynapse(int menu_id)
{
	switch (menu_id)
	{
		case 0:
			break;
		case IDM_SYNAPSECOMMANDS:
			g_config.bSynapseCommands = !g_config.bSynapseCommands;
			break;
		case IDM_SYNAPSEDIRECT:
			g_config.bSynapseDirect = !g_config.bSynapseDirect;
			break;
		case IDM_SYNAPSEFACEMORPHS:
			g_config.bSynapseFaceMorphs = !g_config.bSynapseFaceMorphs;
			break;
		case IDM_SYNAPSECAMERA:
			g_config.bSynapseCamera = !g_config.bSynapseCamera;
			break;
	}
	HMENU hMenuSub = GetSynapseMenu();
	CheckMenuItem(hMenuSub, IDM_SYNAPSECOMMANDS, g_config.bSynapseCommands ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_SYNAPSEDIRECT, g_config.bSynapseDirect ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_SYNAPSEFACEMORPHS, g_config.bSynapseFaceMorphs ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_SYNAPSECAMERA, g_config.bSynapseCamera ? MF_CHECKED : MF_UNCHECKED);
}

//! Show Synapse information dialog.
void HXMainWindow::ClickSynapseID()
{
	ShowWindow(CreateDialog(g_main.hInst, MAKEINTRESOURCE(IDD_SYNAPSEID), m_hFramedWnd, DialogSynapseID), SW_SHOW);
}

//! Show text input dialog.
void HXMainWindow::ClickDirectSpeak()
{
	ShowWindow(CreateDialog(g_main.hInst, MAKEINTRESOURCE(IDD_SPEAKBOX), m_hFramedWnd, DialogDirectSpeak), SW_SHOW);
}

//! Show change voice dialog.
void HXMainWindow::ClickChangeVoice()
{
	ShowWindow(CreateDialog(g_main.hInst, MAKEINTRESOURCE(IDD_CHANGEVOICE), m_hFramedWnd, DialogChangeVoice), SW_SHOW);
}

//! Set the TTS Engine to use.
void HXMainWindow::SelectTTSEngine(int engineid)
{
    HMENU hMenuSub = GetActionMenu();

	// set all items to unchecked
	CheckMenuItem(hMenuSub, IDM_TTSENGINE_MICROSOFTSAPI, MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_TTSENGINE_OPENMARY, MF_UNCHECKED);

    CheckMenuItem(hMenuSub, engineid, MF_CHECKED);

	switch (engineid)
	{
	case IDM_TTSENGINE_MICROSOFTSAPI:
		{
			g_speechtts.SetEngine(HXSAPI);
			break;
		}
	case IDM_TTSENGINE_OPENMARY:
		{
			g_speechtts.SetEngine(HXOpenMary);
			break;
		}
	}
}

//! Activates idle motion of the head (blinking etc).
void HXMainWindow::ClickSetIdle()
{
	HMENU hMenuSub = GetActionMenu();
	if (g_alive.Idle())
	{
		g_alive.Idle() = false;
		g_alive.Clear();
		CheckMenuItem(hMenuSub, IDM_IDLEBEHAVIOUR, MF_UNCHECKED);
		ShowStatus(L"Disabled idle motion");
	}
	else
	{
		g_alive.Idle() = true;
		CheckMenuItem(hMenuSub, IDM_IDLEBEHAVIOUR, MF_CHECKED);
		ShowStatus(L"Activated idle motion");
	}
}

//***********************

//! Escape key handler.
void HXMainWindow::PressEscape()
{
	if (g_director.GetDisplay()->IsLayeredDisplay())
	{
		ClickWindowToggle(HXWT_TRANSPARENT);
	}
	else
	if (!m_bFullscreen && m_hMenu) // no menu
	{
		ClickWindowToggle(HXWT_MENU);
	}
	else
	if (m_bFullscreen && m_hMenu) // fullscreen
	{
		//ClickWindowToggle(HXWT_FULLSCREEN);
		ClickWindowToggle(HXWT_MAXIMIZE);
	}
	else
	if (m_bFullscreen) // maximized
	{
		ClickWindowToggle(HXWT_MAXIMIZE);
	}
	if (!m_bWindowHasBorder) // restore border in all cases
	{
		ClickWindowToggle(HXWT_BORDER);
	}
}

//! Sets the window to a specified size and menu state.
void HXMainWindow::ClickWindowToggle(HXWindowToggle tog)
{
	switch (tog)
	{
	case HXWT_MENU:
		{
			ShowWindow(m_hStatWnd, IsWindowVisible(m_hStatWnd) ? SW_HIDE : SW_SHOW);
			if (!m_hMenu) // a menu is visible
			{
				m_hMenu = GetMenu(m_hFramedWnd);
				SetMenu(m_hFramedWnd, 0);
				g_config.bWindowMenu = false;
			}
			else
			{
				SetMenu(m_hFramedWnd, m_hMenu);
				m_hMenu = 0;
				g_config.bWindowMenu = true;
			}
		}
		break;
	case HXWT_MAXIMIZE:
		{
			if (m_bFullscreen)
			{
				ShowWindow(m_hFramedWnd, SW_RESTORE);
				SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN);
				m_bFullscreen = false;
				g_config.bWindowMaximize = false;
				m_bWindowHasBorder = true;
				g_config.bWindowBorder = true;
			}
			else
			{
				SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_VISIBLE | WS_CLIPCHILDREN);
				ShowWindow(m_hFramedWnd, SW_MAXIMIZE);
				m_bFullscreen = true;
				g_config.bWindowMaximize = true;
				m_bWindowHasBorder = false;
				g_config.bWindowBorder = false;
			}
			SetMenu(m_hFramedWnd, GetMenu(m_hFramedWnd));
		}
		break;
	case HXWT_BORDER:
		{
			if (m_bWindowHasBorder)
			{
				SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_VISIBLE | WS_CLIPCHILDREN);
				m_bWindowHasBorder = false;
				g_config.bWindowBorder = false;
			}
			else
			{
				ShowWindow(m_hFramedWnd, SW_RESTORE);
				SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN);
				m_bFullscreen = false;
				g_config.bWindowMaximize = false;
				m_bWindowHasBorder = true;
				g_config.bWindowBorder = true;
			}
			if (m_hMenu) // menu refresh (sometimes necessary)
			{
				ShowWindow(m_hStatWnd, SW_SHOW);
				ShowWindow(m_hStatWnd, SW_HIDE);
				SetMenu(m_hFramedWnd, m_hMenu);
				SetMenu(m_hFramedWnd, 0);
			}
			else
			{
				ShowWindow(m_hStatWnd, SW_HIDE);
				ShowWindow(m_hStatWnd, SW_SHOW);
				m_hMenu = GetMenu(m_hFramedWnd);
				SetMenu(m_hFramedWnd, 0);
				SetMenu(m_hFramedWnd, m_hMenu);
				m_hMenu = 0;
			}
		}
		break;
	case HXWT_FULLSCREEN:
		{
			if (m_bFullscreen)
			{
				if (m_hMenu)
				{
					ShowWindow(m_hFramedWnd, SW_RESTORE);
					SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN);
					ShowWindow(m_hStatWnd, SW_SHOW);
					SetMenu(m_hFramedWnd, m_hMenu);
					m_hMenu = 0;
					g_config.bWindowMenu = true;
					m_bFullscreen = false;
					g_config.bWindowMaximize = false;
					m_bWindowHasBorder = true;
					g_config.bWindowBorder = true;
				}
				else
				{
					ShowWindow(m_hStatWnd, SW_HIDE);
					m_hMenu = GetMenu(m_hFramedWnd);
					SetMenu(m_hFramedWnd, 0);
					g_config.bWindowMenu = false;
				}
			}
			else
			{
				SetWindowLong(m_hFramedWnd, GWL_STYLE, WS_VISIBLE | WS_CLIPCHILDREN);
				ShowWindow(m_hFramedWnd, SW_MAXIMIZE);
				ShowWindow(m_hStatWnd, SW_HIDE);
				if (!m_hMenu)
				{
					m_hMenu = GetMenu(m_hFramedWnd);
					SetMenu(m_hFramedWnd, 0);
					g_config.bWindowMenu = false;
				}
				m_bFullscreen = true;
				g_config.bWindowMaximize = true;
				m_bWindowHasBorder = false;
				g_config.bWindowBorder = false;
			}  
		}
		break;
	case HXWT_TRANSPARENT:
		if (g_director.GetDisplay()->IsLayeredDisplay())
		{
			g_config.bWindowTransparent = false;
			g_director.AsyncExecute(L"transparent no");
			SetWindowPos(GetMainHandle(), m_bWindowOnTop ? HWND_TOPMOST : HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		}
		else
		{
			g_config.bWindowTransparent = true;
			g_director.AsyncExecute(L"transparent yes");
			SetWindowPos(GetLayerHandle(), m_bWindowOnTop ? HWND_TOPMOST : HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		}  
		break;
	}
	
	UpdateViewMenu();
}

//! Bring the window to the front, permanently (or reverse it).
void HXMainWindow::ClickBringToTop()
{
	if (m_bWindowOnTop)
	{
		SetWindowPos(m_hFramedWnd, 
                 HWND_NOTOPMOST,
				 0, 0,
                 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE); 
		SetWindowPos(m_hLayerWnd, 
                 HWND_NOTOPMOST,
				 0, 0,
                 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE); 
		m_bWindowOnTop = false;
		g_config.bWindowOnTop = false;
	}
	else
	{
		SetWindowPos(m_hFramedWnd, 
                 HWND_TOPMOST,
				 0, 0,
                 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE); 
		SetWindowPos(m_hLayerWnd, 
                 HWND_TOPMOST,
				 0, 0,
                 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE); 
		m_bWindowOnTop = true;
		g_config.bWindowOnTop = true;
	}

	// update menu tick
	HMENU hMenuSub = !m_hMenu ? GetWindowMenu() : 0; // View->Window menu
	CheckMenuItem(hMenuSub, IDM_WINDOW_ONTOP, m_bWindowOnTop ? MF_CHECKED : MF_UNCHECKED);
}

//! Change how the 3D model responds to mouse drags.
void HXMainWindow::ClickMouseMode(HXMouseMode mm)
{
	g_mouse.headMouseMode = mm;
	switch (mm)
	{
	case HXMM_LOCK:
		break;
	case HXMM_CAMERA:
		g_director.GetDisplay()->CameraStatus();
		break;
	case HXMM_LIGHT:
		g_director.GetDisplay()->LightStatus();
		break;
	case HXMM_SUBTITLE:
		g_director.GetDisplay()->SubtitleStatus();
		break;
	case HXMM_WINDOW:
		WindowStatus();
		break;
	}
	UpdateViewMenu();
}

//! Updates the View submenu according to current parameter settings.
void HXMainWindow::UpdateViewMenu()
{
	HMENU hMenuSub = GetMouseMenu();
	CheckMenuItem(hMenuSub, IDM_CAMERA_LOCK, MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_CAMERA, MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_LIGHT, MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_SUBTITLEPOSITION, MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_MOUSE_WINDOW, MF_UNCHECKED);
	switch (g_mouse.headMouseMode)
	{
	case HXMM_LOCK:
		CheckMenuItem(hMenuSub, IDM_CAMERA_LOCK, MF_CHECKED);
		break;
	case HXMM_CAMERA:
		CheckMenuItem(hMenuSub, IDM_CAMERA, MF_CHECKED);
		break;
	case HXMM_LIGHT:
		CheckMenuItem(hMenuSub, IDM_LIGHT, MF_CHECKED);
		break;
	case HXMM_SUBTITLE:
		CheckMenuItem(hMenuSub, IDM_SUBTITLEPOSITION, MF_CHECKED);
		break;
	case HXMM_WINDOW:
		CheckMenuItem(hMenuSub, IDM_MOUSE_WINDOW, MF_CHECKED);
		break;
	}
	
	// update window Synapse state
	unsigned int wstate = 0;
	if (m_hMenu) wstate |= HXWT_MENU;
	if (m_bFullscreen) wstate |= HXWT_MAXIMIZE;
	if (!m_bWindowHasBorder) wstate |= HXWT_BORDER;
	if (g_config.bWindowTransparent) wstate |= HXWT_TRANSPARENT;
	g_link.SetWindowToggle(wstate);

	// update window menu ticks
	hMenuSub = !m_hMenu ? GetWindowMenu() : 0; // View->Window menu
	CheckMenuItem(hMenuSub, IDM_WINDOW_MAXIMIZE, m_bFullscreen ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_WINDOW_BORDER, m_bWindowHasBorder ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hMenuSub, IDM_WINDOW_TRANSPARENT, g_config.bWindowTransparent ? MF_CHECKED : MF_UNCHECKED);
}

//***********************

//! Show About dialog.
void HXMainWindow::ClickAbout()
{
	ShowWindow(CreateDialog(g_main.hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), m_hFramedWnd, DialogAbout), SW_SHOW);
}

//****************************************************************************

//! Applies any loaded configuration options to the current Head X window instance.
void HXMainWindow::ApplyCurrentConfig()
{
	// RENDERING:
	g_director.SetFrameTime(static_cast<int>(1.0f/g_config.renderFPS * 1000));

	// PROCESSING:
	HMENU hSubtitleMenu = GetSubtitleMenu();
	CheckMenuItem(hSubtitleMenu, IDM_SUBTITLEINPUT, g_config.bSubtitleInput ? MF_CHECKED : MF_UNCHECKED);
	if (g_config.bSubtitleInput)
	{
		g_config.bSubtitleOutput = false; // can't have both at the same time
	}
	CheckMenuItem(hSubtitleMenu, IDM_SUBTITLEOUTPUT, g_config.bSubtitleOutput ? MF_CHECKED : MF_UNCHECKED);
	if (!(g_config.bSubtitleInput || g_config.bSubtitleOutput))
	{
		g_subtitle.Clear();
	}

	HMENU hActionMenu = GetActionMenu();
	CheckMenuItem(hActionMenu, IDM_ECHOINPUTS, g_config.bEchoInputs ? MF_CHECKED : MF_UNCHECKED);
	CheckMenuItem(hActionMenu, IDM_PARSEINPUT, g_config.bCurlyTags ? MF_CHECKED : MF_UNCHECKED);
	if ((g_config.bIdleBehaviour && !g_alive.Idle()) || (!g_config.bIdleBehaviour && g_alive.Idle()))
	{
		ClickSetIdle();
	}
	if (g_talk.BreakOnStop() != g_config.bBreakOnStop)
	{
		ClickBreakOnStop();
	}

	// SYNAPSE:
	ClickSynapse();

	// HEAD ROTATION:
	if (g_config.interactionMode.empty() || (WStringToLowerCase(g_config.interactionMode) == L"camera"))
	{
		g_mouse.headMouseMode = HXMM_CAMERA; //default
	}
	else
	if (WStringToLowerCase(g_config.interactionMode) == L"light")
	{
		g_mouse.headMouseMode = HXMM_LIGHT;
	}
	else
	if (WStringToLowerCase(g_config.interactionMode) == L"subtitle")
	{
		g_mouse.headMouseMode = HXMM_SUBTITLE;
	}
	else
	if (WStringToLowerCase(g_config.interactionMode) == L"lock")
	{ 
		g_mouse.headMouseMode = HXMM_LOCK;
	}
	else
	if (WStringToLowerCase(g_config.interactionMode) == L"window")
	{ 
		g_mouse.headMouseMode = HXMM_WINDOW;
	}
	UpdateViewMenu();
	g_director.ResetLights(true);

	// TTS:
	if (WStringToLowerCase(g_config.voiceEngine) == L"sapi")
	{
		SelectTTSEngine(IDM_TTSENGINE_MICROSOFTSAPI);
	}
	else 
	if (WStringToLowerCase(g_config.voiceEngine) == L"mary")
	{
		SelectTTSEngine(IDM_TTSENGINE_OPENMARY);
	}
	else
	{
		ShowErrorStatus(L"Unknown TTS engine defined in configuration file: %s", g_config.voiceEngine.c_str());
	}
	g_speechtts.SetVoice(WStringToString(g_config.voiceName));
	g_speechtts.SetEnginekHz(g_config.voicekHz);
	g_speechtts.SetRate(g_config.voiceRate);
	g_speechtts.SetAVSyncAdjust(g_config.voiceAVSync);
	g_speechtts.SetRampFunc(g_config.rampFunc);
	g_speechtts.SetVisemeModulator(g_config.voiceModulator);
	g_speechtts.SetOverlap(g_config.voiceOverlap);
	g_speechtts.SetFPS(g_config.renderFPS);
	g_speechtts.SetMix(g_config.mixFrames, g_config.mixFactor);

	g_speechtts.SetVisemeMap(g_config.visemeMap);

	// WINDOW:
	if ((g_config.windowXPos >= 0) && (g_config.windowYPos >= 0))
	{
		Reposition(g_config.windowXPos, g_config.windowYPos);
		g_config.windowXPos = -1;
		g_config.windowYPos = -1;
	}
	if ((g_config.windowWidth >= 0) && (g_config.windowHeight >= 0))
	{
		Resize(g_config.windowWidth, g_config.windowHeight);
		g_config.windowWidth = -1;
		g_config.windowHeight = -1;
	}
	
	if ((g_config.bWindowMenu && m_hMenu) || (!g_config.bWindowMenu && !m_hMenu))
	{
		ClickWindowToggle(HXWT_MENU);
	}
	if ((g_config.bWindowMaximize && !m_bFullscreen) || (!g_config.bWindowMaximize && m_bFullscreen))
	{
		ClickWindowToggle(HXWT_MAXIMIZE);
		ClickWindowToggle(HXWT_MENU); // Ogl resize problem otherwise
		ClickWindowToggle(HXWT_MENU);
	}
	if ((g_config.bWindowBorder && !m_bWindowHasBorder) || (!g_config.bWindowBorder && m_bWindowHasBorder)) 
	{
		ClickWindowToggle(HXWT_BORDER);
	}
	if (g_config.windowTitle.size())
	{
		SetWindowText(m_hFramedWnd, g_config.windowTitle.c_str());
	}
	if ((g_config.bWindowOnTop && !m_bWindowOnTop) || (!g_config.bWindowOnTop && m_bWindowOnTop))
	{
		ClickBringToTop();
	}
	if ((g_config.bWindowTransparent && !g_director.GetDisplay()->IsLayeredDisplay()) || (!g_config.bWindowTransparent && g_director.GetDisplay()->IsLayeredDisplay()))
	{
		ClickWindowToggle(HXWT_TRANSPARENT);
	}

	// DYNAMIC BACKGROUND:
	if (g_config.dynBGSynapse.size() && !g_link.OpenBackground(WStringToString(g_config.dynBGSynapse), g_config.dynBGX, g_config.dynBGY))
	{
		ShowErrorStatus(L"Invalid background buffer of name \"%s\" with width %d and height %d", g_config.dynBGSynapse.c_str(), g_config.dynBGX, g_config.dynBGY);
	}

	UpdateQuickMenu();

	ShowStatus(L"Configuration applied and ready");
}

void HXMainWindow::WindowStatus()
{
	RECT r;
	GetWindowRect(m_hFramedWnd, &r);
	ShowStatus(L"Window: x=%d y=%d width=%d height=%d", r.left, r.top, r.right - r.left, r.bottom - r.top);
}

//****************************************************************************

//! Creates a status bar and divides it into the specified number of parts (nParts).
HWND HXMainWindow::CreateStatusBar(HWND hwndParent, int nStatusID, HINSTANCE hinst, int nParts)
{
    HWND hwndStatus;
    RECT rcClient;
    HLOCAL hloc;
    LPINT lpParts;
    int i, nWidth;

    // Ensure that the common control DLL is loaded
    InitCommonControls();

    // Create the status bar
    hwndStatus = CreateWindowEx(
        0,                       // no extended styles
        STATUSCLASSNAME,         // name of status bar class
        (LPCTSTR) NULL,          // no text when first created
        SBARS_SIZEGRIP |         // includes a sizing grip
		WS_VISIBLE |			 // window visible
        WS_CHILD,                // creates a child window
        0, 0, 0, 0,              // ignores size and position
        hwndParent,              // handle to parent window
        (HMENU) nStatusID,       // child window identifier
        hinst,                   // handle to application instance
        NULL);                   // no window creation data

    // Get the coordinates of the parent window's client area
    GetClientRect(hwndParent, &rcClient);

    // Allocate an array for holding the right edge coordinates
    hloc = LocalAlloc(LHND, sizeof(int) * nParts);
    lpParts = (int*)LocalLock(hloc);

    // Calculate the right edge coordinate for each part, and
    // copy the coordinates to the array
    nWidth = rcClient.right / nParts;
    for (i = 0; i < nParts; i++) { 
       lpParts[i] = nWidth;
       nWidth += nWidth;
    }

    // Tell the status bar to create the window parts
    SendMessage(hwndStatus, SB_SETPARTS, (WPARAM) nParts, (LPARAM)
               lpParts);

    // Free the array, and return
    LocalUnlock(hloc);
    LocalFree(hloc);
    return hwndStatus;
}

//-----------------------------------------------------------------------------

//! Constructor.
HXLogger::HXLogger() 
: m_logFile(0) 
{ 
	m_startClock = clock(); 
	m_hLogMutex = CreateMutex(NULL, FALSE, NULL);
}

//! Destructor.
HXLogger::~HXLogger()
{
	Close();
	CloseHandle(m_hLogMutex);
}

//! Open or create a log file.
bool HXLogger::Open(wstring& filename)
{
	if (m_logFile)
	{
		if (!Close())
		{
			return false;
		}
	}
	if (filename.empty())
	{
		return false;
	}
	m_logFile = CreateFile(filename.c_str(),
						   GENERIC_WRITE,
						   FILE_SHARE_READ,
						   NULL,
						   OPEN_ALWAYS,
						   FILE_ATTRIBUTE_NORMAL,
						   NULL);


	if (SUCCEEDED(m_logFile))
	{
		return true;
	}
	else
	{
		g_window.ShowErrorStatus(L"Cannot open log file \"%s\" - logging will be disabled", filename.c_str());
		return false;
	}
}

//! Close the log file.
bool HXLogger::Close()
{
	if (m_logFile)
	{
		if (CloseHandle(m_logFile))
		{
			m_logFile = 0;
			return true;
		}
		else
		{
			g_window.ShowStatus(L"Cannot close log file");
			return false;
		}
	}
	return true;
}

//! Write string to the log file (if open).
void HXLogger::Log(const string& text, wchar_t logtype)
{
	if (m_logFile) // to eliminate conversion cost if there is no file
	{
		Log(StringToWString(text), logtype);
	}
}

//! Write characters to the log file (if open).
void HXLogger::Log(const char* text, wchar_t logtype)
{
	if (m_logFile)
	{
		Log(StringToWString(string(text)), logtype);
	}
}

//! Write wide string to the log file (if open).
void HXLogger::Log(const wstring& text, wchar_t logtype)
{
	Log(text.c_str(), logtype);
}

//! Write wide characters to the log file (if open).
void HXLogger::Log(const wchar_t* text, wchar_t logtype)
{
	if (m_logFile)
	{
		WaitForSingleObject(m_hLogMutex, INFINITE);
		double duration = (double)(clock() - m_startClock)/CLOCKS_PER_SEC;
		swprintf(m_logBuffer, MAX_TEXT_LENGTH, L"@%c/%.3f@ %s\r\n", logtype, duration, text);
		OVERLAPPED o;
		memset(&o, 0, sizeof(o));
		o.Offset = 0xffffffff; //FILE_WRITE_TO_END_OF_FILE
		o.OffsetHigh = static_cast<DWORD>(-1);
		DWORD dwBytesWritten;
		WriteFile(m_logFile, m_logBuffer, wcslen(m_logBuffer) * sizeof(wchar_t), &dwBytesWritten, &o);
		//fwprintf(m_logFile, L"@%c/%.3f@ %s\n", logtype, duration, text);
		//fflush(m_logFile);
		ReleaseMutex(m_hLogMutex);
	}
}

//-----------------------------------------------------------------------------

//! Constructor.
HXSubtitle::HXSubtitle()
: m_subtitleLength(0),
  m_subtitlePromptLength(0),
  m_subtitleClock(0),
  m_subSpokenRatio(1.0f)
{
	m_subtitle[0] = 0;
	m_subtitlePrompt[0] = 0;
}

//! Compute visible subtitles for rendering.
void HXSubtitle::Generate()
{
	if (g_config.bSubtitleInput || g_config.bSubtitleOutput)
	{
		if (g_config.bSubtitleOutput)
		{
			if (clock() > m_subtitleClock + 250)
			{
				if (g_link.GetIsTalking() >= HXSY_SPEAKING)
				{
					char buffer[2*MAX_TEXT_LENGTH];
					strcpy_s(buffer, MAX_TEXT_LENGTH, WStringToString(g_config.subtitleOutputPrefix).c_str());
					strcpy_s(buffer+strlen(buffer), MAX_TEXT_LENGTH, m_spokenText.c_str());
					m_subtitle[0] = 0; // to prevent old stuff showing up if nothing is said
					if (g_config.voiceEngine == L"SAPI")
					{
						int wordIndex = static_cast<int>(g_link.GetWordIndex() * m_subSpokenRatio);
						bool exitSoon = false;
						size_t endPoint = 0;
						char *word;
						char *next_token;
     					word = strtok_s(buffer, " \t\n\r", &next_token);
						if (word)
						{
							do
							{
								if (wordIndex-- <= 0)
								{
									exitSoon = true;
								}
								wcscpy_s(m_subtitle+endPoint, MAX_TEXT_LENGTH-endPoint, StringToWString(string(word)).c_str());
								wcscat_s(m_subtitle, MAX_TEXT_LENGTH, L" ");
								endPoint += strlen(word)+1;
								if (endPoint > static_cast<size_t>(g_config.subtitleLength))
								{
									if (exitSoon)
									{
										break;
									}
									endPoint = 0;
								}
							}
							while (word = strtok_s(NULL, " \t\n\r", &next_token));
						}
						m_subtitleLength = endPoint;
					}
					else
					{
						wcscpy_s(m_subtitle, MAX_TEXT_LENGTH, StringToWString(string(buffer)).c_str());
					}
				}
				else
				if (m_subtitle[0])
				{
					m_subtitle[0] = 0;
					m_subtitleLength = 0;
				}
				m_subtitleClock = clock();
			}
		}
		if (g_config.bSubtitleInput)
		{
			wcscpy_s(m_subtitle, MAX_TEXT_LENGTH, g_config.subtitleInputPrefix.c_str());
			wcscat_s(m_subtitle, MAX_TEXT_LENGTH, m_subtitlePrompt);
			m_subtitleLength = m_subtitlePromptLength + g_config.subtitleInputPrefix.size();
			if ((clock() % g_config.subtitleBlinkDelay) > (g_config.subtitleBlinkDelay/2))
			{
				wcscat_s(m_subtitle, MAX_TEXT_LENGTH, L"_");
			}
		}
	}
}

//! Update subtitle prompt with character.
void HXSubtitle::PromptKey(char charcode)
{
	if (m_subtitlePromptLength <= g_config.subtitleLength) 
	{
		m_subtitlePrompt[m_subtitlePromptLength++] = charcode;
		m_subtitlePrompt[m_subtitlePromptLength] = 0;
	}
}

//! Delete last character from subtitle prompt.
void HXSubtitle::PromptBack()
{
	if (m_subtitlePromptLength > 0) 
	{
		m_subtitlePrompt[--m_subtitlePromptLength] = 0;
	}
}

//! Process and reset current subtitle prompt.
void HXSubtitle::PromptEnter()
{
	if (m_subtitlePromptLength > 0) 
	{
		if (g_config.bEchoInputs)
		{
			AsyncSpeak(wstring(m_subtitlePrompt));
			g_window.ShowStatus(L"Speaking text: \"%s\"", m_subtitlePrompt);
		}
		else
		{
			g_logger.Log(m_subtitlePrompt, L'I');
			g_link.SetUserText(WStringToString(wstring(m_subtitlePrompt)).c_str());
		}
		m_subtitlePromptLength = 0;
		m_subtitlePrompt[m_subtitlePromptLength] = 0;
	}
}

//! Calculate the ratio of words in the subtitle versus those that will be spoken.
void HXSubtitle::SetSubtitleSpokenRatio(const wstring& subtitle, const wstring& spoken)
{
	wchar_t *word, *next_token;
	wchar_t subBuffer[MAX_TEXT_LENGTH], spokenBuffer[MAX_TEXT_LENGTH];
	size_t subCount = 0, spokenCount = 0;
	wcscpy_s(subBuffer, MAX_TEXT_LENGTH, subtitle.c_str());
	wcscpy_s(spokenBuffer, MAX_TEXT_LENGTH, spoken.c_str());
    word = wcstok_s(subBuffer, L" \t\n\r", &next_token);
	do
	{
		subCount++;
	} 
	while (word = wcstok_s(NULL, L" \t\n\r", &next_token));
	word = wcstok_s(spokenBuffer, L" \t\n\r", &next_token);
	do
	{
		spokenCount++;
	} 
	while (word = wcstok_s(NULL, L" \t\n\r", &next_token));
	m_subSpokenRatio = static_cast<float>(subCount)/static_cast<float>(spokenCount);
}

//-----------------------------------------------------------------------------

